0%

JAVA rmi的实现及源码解析

前言:rmi(remote method invocation)是java官方的远程调用的是一种实现方式,它使得我们能像调用本地服务一般调用远程服务

rmi

源码分析

1. 客户端调用

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.rmi.Naming;

/**
* TODO 类描述
*
* @author xcw
*/
public class ClientApplication {

public static void main(String args[]) {
String url = "rmi://localhost:8888/";
try {
// 在RMI服务注册表中查找名称为server-service的对象,并调用其上的方法
//Naming.lookup(registry url+service name)将会返回Remote接口
ServerService service = (ServerService) Naming.lookup(url + "server-service");

Class stubClass = service.getClass();
System.out.println(service + " 是 " + stubClass.getName() + " 的实例!");

// 获得本底存根已实现的接口类型
Class[] interfaces = stubClass.getInterfaces();
for (Class c : interfaces) {
System.out.println("存根类实现了 " + c.getName() + " 接口!");
}
System.out.println(service.service());
} catch (Exception e) {
e.printStackTrace();
}
}
}

分析

1. 在RMI服务注册表中查找服务接口, Naming.lookup(registry url+service name)将会返回

Proxy[ServerService,RemoteObjectInvocationHandler[UnicastRef [liveRef: [endpoint:192.168.1.103:49471,objID:[-79051ded:16cec85dc68:-7fff, -7771089289704678398]]]]]

即ServerService的动态代理对象

1
ServerService service = (ServerService) Naming.lookup(url + "server-service");
2. 进入Naming.lookup方程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Returns a reference, a stub, for the remote object associated
* with the specified <code>name</code>.
*
* @param name a name in URL format (without the scheme component)
* @return a reference for a remote object
* @exception NotBoundException if name is not currently bound
* @exception RemoteException if registry could not be contacted
* @exception AccessException if this operation is not permitted
* @exception MalformedURLException if the name is not an appropriately
* formatted URL
* @since JDK1.1
*/
public static Remote lookup(String name)
throws NotBoundException,
java.net.MalformedURLException,
RemoteException
{
ParsedNamingURL parsed = parseURL(name);
Registry registry = getRegistry(parsed);

if (parsed.name == null)
return registry;
return registry.lookup(parsed.name);
}
1
Registry registry = getRegistry(parsed);

Registry is a remote interface to a simple remote
object registry that provides methods for storing and retrieving remote object references bound with arbitrary string names.

3. Registry接口帮我们拿到我们想要的指定名称的远程服务的reference 即RegistryImpl_Stub
  • 进入registry.lookup(parsed.name)

    调用 RegistryImpl_Stub的ref(RemoteRef)对象的newCall()方法,将RegistryImpl_Stub对象传了进去,不要忘了构造它的时候我们将服务器的主机端口等信息传了进去,也就是我们把服务器相关的信息也传进了newCall()方法。newCall()方法做的事情简单来看就是建立了跟远程RegistryImpl的Skeleton对象的连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
try {
RemoteCall var2 = this.ref.newCall(this, operations, 2, 4905912898345647071L);

try {
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(var1);
} catch (IOException var17) {
throw new MarshalException("error marshalling arguments", var17);
}

this.ref.invoke(var2);

Remote var22;
try {
ObjectInput var4 = var2.getInputStream();
var22 = (Remote)var4.readObject();
} catch (IOException var14) {
throw new UnmarshalException("error unmarshalling return", var14);
} catch (ClassNotFoundException var15) {
throw new UnmarshalException("error unmarshalling return", var15);
} finally {
this.ref.done(var2);
}

return var22;
} catch (RuntimeException var18) {
throw var18;
} catch (RemoteException var19) {
throw var19;
} catch (NotBoundException var20) {
throw var20;
} catch (Exception var21) {
throw new UnexpectedException("undeclared checked exception", var21);
}
}
4. 客户端获取服务端返回的服务Stub对象,接下来可以利用Stub对象进行远程调用

2. 服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

/**
* TODO 类描述
*
* @author xcw
*/
public class ServerApplication {

public static void main(String args[]) {
try {
//实例化ServerServiceImpl
ServerService service = new ServerServiceImpl();

// 本地主机上的远程对象注册表Registry的实例,并指定端口为8888,这一步必不可少(Java默认端口是1099
// ),必不可缺的一步,缺少注册表创建,则无法绑定对象到远程注册表上
LocateRegistry.createRegistry(8888);

//绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)
Naming.bind("rmi://localhost:8888/server-service", service);


} catch (Exception e) {
e.printStackTrace();
}

System.out.println("服务器向命名表注册了1个远程服务对象!");
}
}
1. LocateRegistry.createRegistry(8888)创建RegistryImpl对象

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public RegistryImpl(final int var1) throws RemoteException {
this.bindings = new Hashtable(101);
if (var1 == 1099 && System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
public Void run() throws RemoteException {
LiveRef var1x = new LiveRef(RegistryImpl.id, var1);
RegistryImpl.this.setup(new UnicastServerRef(var1x, (var0) -> {
return RegistryImpl.registryFilter(var0);
}));
return null;
}
}, (AccessControlContext)null, new SocketPermission("localhost:" + var1, "listen,accept"));
} catch (PrivilegedActionException var3) {
throw (RemoteException)var3.getException();
}
} else {
LiveRef var2 = new LiveRef(id, var1);
this.setup(new UnicastServerRef(var2, RegistryImpl::registryFilter));
}

}
1
2
3
RegistryImpl.this.setup(new UnicastServerRef(var1x, (var0) -> {
return RegistryImpl.registryFilter(var0);
}));

setUp()方法将指向正在初始化的RegistryImpl对象的远程引用ref(RemoteRef)赋值为传入的UnicastServerRef对象,这里涉及了向上转型。然后继续移交UnicastServerRef的exportObject()方法。

进入UnicastServerRef的exportObject()方法。可以看到,这里首先为传入的RegistryImpl创建一个代理,这个代理我们可以推断出就是后面服务于客户端的RegistryImpl的Stub对象。然后将UnicastServerRef的skel(skeleton)对象设置为当前RegistryImpl对象。最后用skeleton、stub、UnicastServerRef对象、id和一个boolean值构造了一个Target对象,也就是这个Target对象基本上包含了全部的信息。调用UnicastServerRef的ref(LiveRef)变量的exportObject()方法。

到上面为止,我们看到的都是一些变量的赋值和创建工作,还没有到连接层,这些引用对象将会被Stub和Skeleton对象使用。接下来就是连接层上的了。追溯LiveRef的exportObject()方法,很容易找到了TCPTransport的exportObject()方法。这个方法做的事情就是将上面构造的Target对象暴露出去。调用TCPTransport的listen()方法,listen()方法创建了一个ServerSocket,并且启动了一条线程等待客户端的请求。接着调用父类Transport的exportObject()将Target对象存放进ObjectTable中

2. 将服务实现绑定到服务端的RegistryImpl上,使得客户端只需与RegistryImpl_Stub交互

总结

  • 当客户端通过RMI注册表找到一个远程接口的时候,所得到的其实是远程接口的一个动态代理对象。
  • 当客户端调用其中的方法的时候,方法的参数对象会在序列化之后,传输到服务器端
  • 服务器端接收到之后,进行反序列化得到参数对象
  • 并使用这些参数对象,在服务器端调用实际的方法
  • 调用的返回值Java对象经过序列化之后,再发送回客户端
  • 客户端再经过反序列化之后得到Java对象,返回给调用者
  • 这中间的序列化过程对于使用者来说是透明的,由动态代理对象自动完成

Stub和Skeleton的作用

  • Stub对象做的事情是建立到服务端Skeleton对象的Socket连接。将客户端的方法调用转换为字符串标识传递给Skeleton对象。并且同步阻塞等待服务端返回结果
  • Skeleton对象做的事情是将服务实现传入构造参数,获取客户端通过socket传过来的方法调用字符串标识,将请求转发到具体的服务上面。获取结果之后返回给客户端。